package com.bins.uaa.auth.service;

import com.alibaba.fastjson.JSON;
import com.bins.auth.api.vo.AuthCode;
import com.bins.auth.api.vo.AuthToken;
import com.bins.uaa.common.exception.ExceptionCast;
import com.bins.uaa.common.server.ServiceList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class AuthService {

    final LoadBalancerClient loadBalancerClient;

    final RestTemplate restTemplate;

    final StringRedisTemplate redisTemplate;

    @Value("${auth.tokenValiditySeconds}")
    int tokenValiditySeconds;

    public AuthService(LoadBalancerClient loadBalancerClient, RestTemplate restTemplate, StringRedisTemplate redisTemplate) {
        this.loadBalancerClient = loadBalancerClient;
        this.restTemplate = restTemplate;
        this.redisTemplate = redisTemplate;
    }

    public AuthToken login(String username, String password, String clientId, String clientSecret) {
        AuthToken authToken = this.applyToken(username, password, clientId, clientSecret);
        String access_token = authToken.getAccess_token();
        String content = JSON.toJSONString(authToken);

        boolean token = this.saveToken(access_token, content, tokenValiditySeconds);
        if (!token) {
            ExceptionCast.cast(AuthCode.AUTH_LOGIN_TOKEN_SAVEFAIL);
        }
        return authToken;
    }

    private boolean saveToken(String access_token, String content, long ttl) {
        String key = "user_token:" + access_token;
        redisTemplate.boundValueOps(key).set(content, ttl, TimeUnit.SECONDS);
        Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        if (expire == null) {
            expire = 0L;
        }
        return expire > 0;
    }

    private AuthToken applyToken(String username, String password, String clientId, String clientSecret) {
        ServiceInstance serviceInstance = loadBalancerClient.choose(ServiceList.SERVICE_AUTH);
        URI uri = serviceInstance.getUri();
        String authUrl = uri + "/auth/oauth/token";
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        String httpbasic = httpbasic(clientId, clientSecret);
        headers.add("Authorization", httpbasic);
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "password");
        body.add("username", username);
        body.add("password", password);
        HttpEntity<MultiValueMap<String, String>> multiValueMapHttpEntity = new HttpEntity<>(body, headers);
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
                    super.handleError(response);
                }
            }
        });

        ResponseEntity<Map> exchange = restTemplate.exchange(authUrl, HttpMethod.POST, multiValueMapHttpEntity, Map.class);
        Map body1 = exchange.getBody();
        if (body1 == null ||
                body1.get("access_token") == null ||
                body1.get("refresh_token") == null ||
                body1.get("jti") == null) {
            if (body1 != null && body1.get("error_description") != null) {
                String error_description = (String) body1.get("error_description");
                if (error_description.contains("UserDetailsService returned null")) {
                    ExceptionCast.cast(AuthCode.AUTH_ACCOUNT_NOTEXISTS);
                } else if (error_description.contains("坏的凭证")) {
                    ExceptionCast.cast(AuthCode.AUTH_CREDENTIAL_ERROR);
                }
            }
            return null;
        }
        AuthToken authToken = new AuthToken();
        authToken.setAccess_token((String) body1.get("jti"));
        authToken.setRefresh_token((String) body1.get("refresh_token"));
        authToken.setJwt_token((String) body1.get("access_token"));
        return authToken;
    }

    private String httpbasic(String clientId, String clientSecret) {
        String string = clientId + ":" + clientSecret;
        byte[] encode = Base64Utils.encode(string.getBytes());
        return "Basic " + new String(encode);
    }

    public AuthToken getUserToken(Long userId) {
        String key = "user_token:" + userId;
        String value = redisTemplate.opsForValue().get(key);
        try {
            return JSON.parseObject(value, AuthToken.class);
        } catch (Exception e) {
            log.error("getUserToken from redis and execute JSON parseObject error {}", e.getMessage());
            return null;
        }
    }

    public Boolean delToken(Long uid) {
        String key = "user_token:" + uid;
        redisTemplate.delete(key);
        return Boolean.TRUE;
    }
}
